/*
 * Copyright (c) 2013 Björn Aili
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 * distribution.
 */
#ifndef FTL_TYPE_TRAITS_H
#define FTL_TYPE_TRAITS_H

#include <utility>
#include <iterator>
#include <functional>

#define FTL_GEN_PREUNOP_TEST(op, name)\
	template<typename T>\
	auto test_ ## name (typename std::remove_reference<\
			decltype(op std::declval<T&>())\
	>::type*)\
	-> decltype(op std::declval<T&>());\
	\
	template<typename T>\
	no test_ ## name (...)

#define FTL_GEN_POSTUNOP_TEST(op, name)\
	template<typename T>\
	auto test_ ## name (typename std::remove_reference<\
			decltype(std::declval<T&>() op)\
	>::type*)\
	-> decltype(std::declval<T&>() op);\
	\
	template<typename T>\
	no test_ ## name (...)

#define FTL_GEN_BINOP_TEST(op, name)\
	template<typename T>\
	auto test_ ## name (decltype(std::declval<T>() op std::declval<T>())*)\
	-> decltype(std::declval<T>() op std::declval<T>());\
	\
	template<typename T>\
	no test_ ## name (...)

#define FTL_GEN_UNFN_TEST(fn, name)\
	template<typename T>\
	bool test_ ## name (decltype( fn (std::declval<T>()))*);\
	\
	template<typename T>\
	no test_ ## name (...)

#define FTL_GEN_BINFN_TEST(fn, name)\
	template<typename T>\
	bool test_ ## name (decltype( fn (std::declval<T>(), std::declval<T>()))*);\
	\
	template<typename T>\
	no test_ ## name (...)

#define FTL_GEN_METH0_TEST(name)\
	template<typename T>\
	bool test_ ## name (decltype(std::declval<T>() . name ())*);\
	\
	template<typename T, typename U>\
	no test_ ## name (...)

#define FTL_GEN_METH1_TEST(name)\
	template<typename T, typename U>\
	bool test_ ## name (decltype(std::declval<T>() . name (std::declval<U>()))*);\
	\
	template<typename T, typename U>\
	no test_ ## name (...)

#define FTL_GEN_METH2_TEST(name)\
	template<typename T, typename U, typename V>\
	auto test_ ## name (\
		decltype(std::declval<T>() . name (std::declval<U>(), std::declval<V>()))*\
	)\
	-> decltype(std::declval<T>() . name (std::declval<U>(), std::declval<V>()));\
	\
	template<typename, typename, typename>\
	no test_ ## name (...)

#define FTL_GEN_TYPEMEM_TEST(name)\
	template<typename T>\
	auto test_typemem_ ## name (\
		typename std::remove_reference<\
			typename T :: name\
		>::type *\
	) -> typename T :: name ;\
	\
	template<typename T>\
	no test_typemem_ ## name (...)

namespace ftl {
	/**
	 * \defgroup typetraits Type Traits
	 *
	 * Collection of useful type traits.
	 *
	 * The main difference between this module and \ref typelevel is that
	 * this module is concerned with finding out particular properties of types,
	 * \ref typelevel is concerned with _modifying_ types.
	 *
	 * \code
	 *   #include <ftl/type_traits.h>
	 * \endcode
	 *
	 * \par Dependencies
	 * - <utility>
	 * - <iterator>
	 * - <functional>
	 */

	namespace _dtl {
		struct no {};

		FTL_GEN_BINOP_TEST(==, eq);
		FTL_GEN_BINOP_TEST(!=, neq);
		FTL_GEN_BINOP_TEST(<, lt);
		FTL_GEN_BINOP_TEST(<, gt);

		FTL_GEN_UNFN_TEST(begin, begin);
		FTL_GEN_METH0_TEST(rbegin);
		FTL_GEN_UNFN_TEST(end, end);
		FTL_GEN_METH0_TEST(rend);

		FTL_GEN_METH1_TEST(push_back);
	}

	// The type functions below could have been autogenerated, but this way
	// they're easier to document.

	/**
	 * Type trait to test for `operator==()`
	 *
	 * Example:
	 * \code
	 *   template<typename T>
	 *   auto foo() -> Requires<ftl::has_eq<T>::value> {
	 *       // ...
	 *       if(some_t == other_t)
	 *       // ...
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_eq {
		static constexpr bool value = 
			std::is_convertible<
				decltype(_dtl::test_eq<T>(nullptr)),
				bool
			>::value;
	};

	/**
	 * Test a type for `operator!=()`.
	 *
	 * \tparam T To satisfy `has_new`, there must be an `operator!=` accepting
	 *           either two const references or two values of type `T` and
	 *           returning something that is contextually convertible to `bool`.
	 *
	 * Example:
	 * \code
	 *   template<typename T>
	 *   auto foo() -> Requires<ftl::has_neq<T>::value> {
	 *       // ...
	 *       if(some_t != other_t)
	 *       // ...
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_neq {
		static constexpr bool value = 
			std::is_convertible<
				decltype(_dtl::test_neq<T>(nullptr)),
				bool
			>::value;
	};


	/**
	 * Test a type for `operator<()`
	 *
	 * \tparam T Satisfies the predicate if there exists an `operator<`
	 *           taking either two `T` or two `const T&` and returning something
	 *           contextually convertible to `bool`.
	 *
	 * Example:
	 * \code
	 *   template<typename T>
	 *   auto foo() -> Requires<ftl::has_lt<T>::value> {
	 *       // ...
	 *       if(some_t < other_t)
	 *       // ...
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_lt {
		static constexpr bool value =
			std::is_convertible<
				decltype(_dtl::test_lt<T>(nullptr)),
				bool
			>::value;
	};

	/**
	 * Test a type for `operator>()`
	 *
	 * Example:
	 * \code
	 *   template<typename T>
	 *   auto foo() -> Requires<ftl::has_gt<T>::value> {
	 *       // ...
	 *       if(some_t > other_t)
	 *       // ...
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_gt {
		static constexpr bool value =
			std::is_convertible<
				decltype(_dtl::test_gt<T>(nullptr)),
				bool
			>::value;
	};

	/**
	 * Test a type for `operator++()`.
	 *
	 * Example:
	 * \code
	 *   template<typename T>
	 *   auto foo() -> Requires<ftl::has_pre_inc<T>::value> {
	 *       // ...
	 *       ++some_t;
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_pre_inc {
	private:
		template<typename X>
		static auto check(X& x) -> decltype(++x);
		static _dtl::no check(...);

	public:
		static constexpr bool value =
			!std::is_same<_dtl::no, decltype(check(std::declval<T&>()))>::value;
	};

	/**
	 * Test a type for `operator++(int)`.
	 *
	 * Example:
	 * \code
	 *   template<typename T>
	 *   auto foo()
	 *   -> Requires<ftl::has_post_inc<T>::value> {
	 *       // ...
	 *       some_t++;
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_post_inc {
	private:
		template<typename X>
		static auto check(X& x) -> decltype(x++);
		static _dtl::no check(...);

	public:
		static constexpr bool value =
			!std::is_same<_dtl::no, decltype(check(std::declval<T&>()))>::value;
	};

	/**
	 * Test a type for `std::begin()` compatibility.
	 *
	 * Example:
	 * \code
	 *   template<
	 *   	typename T,
	 *   	typename = Requires<
	 *   	    ftl::has_begin<T>::value
	 *   	    && ftl::has_end<T>::value
	 *   	>
	 *   >
	 *   void foo() {
	 *       for(auto& e : some_t) {
	 *           // ...
	 *       }
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_begin {
		static constexpr bool value =
			!std::is_same<
				_dtl::no,
				decltype(_dtl::test_begin<T>(nullptr))
			>::value;
	};

	// TODO: C++14 - std::rbegin
	/**
	 * Test a type for `T::rbegin()` compatibility.
	 *
	 * Example:
	 * \code
	 *   template<
	 *   	typename T,
	 *   	typename = Requires<
	 *   	    ftl::has_rbegin<T>::value
	 *   	    && ftl::has_rend<T>::value
	 *   	>
	 *   >
	 *   void foo() {
	 *       for(auto it = some_t.rbegin(); it != some_t.rend(); ++it) {
	 *           // ...
	 *       }
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_rbegin {
		static constexpr bool value =
			!std::is_same<
				_dtl::no,
				decltype(_dtl::test_rbegin<T>(nullptr))
			>::value;
	};

	/**
	 * Test a type for `std::end()` compatibility.
	 *
	 * Example:
	 * \code
	 *   template<
	 *   	typename T,
	 *   	typename = Requires<
	 *   	    ftl::has_begin<T>::value
	 *   	    && ftl::has_end<T>::value
	 *   	>
	 *   >
	 *   void foo() {
	 *       for(auto& e : some_t) {
	 *           // ...
	 *       }
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_end {
		static constexpr bool value =
			!std::is_same<
				_dtl::no,
				decltype(_dtl::test_end<T>(nullptr))
			>::value;
	};

	/**
	 * Test a type for `T::rend()` compatibility.
	 *
	 * Example:
	 * \code
	 *   template<
	 *   	typename T,
	 *   	typename = Requires<
	 *   	    ftl::has_rbegin<T>::value
	 *   	    && ftl::has_rend<T>::value
	 *   	>
	 *   >
	 *   void foo() {
	 *       for(auto it = some_t.rbegin(); it != some_t.rend(); ++it) {
	 *           // ...
	 *       }
	 *   }
	 * \endcode
	 *
	 * \ingroup typetraits
	 */
	template<typename T>
	struct has_rend {
		static constexpr bool value =
			!std::is_same<
				_dtl::no,
				decltype(_dtl::test_rend<T>(nullptr))
			>::value;
	};

	template<typename T, typename U>
	struct has_push_back {
		static constexpr bool value =
			!std::is_same<
				_dtl::no,
				decltype(_dtl::test_push_back<T,U>(nullptr))
			>::value;
	};

	template<typename>
	class function;

	/**
	 * Checks if a certain type is a monomorphic function object.
	 *
	 * This check can be used to distinguish objects that have several
	 * `operator()` overloads from objects that have exactly one. Note that
	 * this check does not work on arbitrary user types; it must be specialised
	 * for each case.
	 *
	 * Built in specialisations include:
	 * - `std::function`
	 * - function pointers and pointers to member functions
	 * - `ftl::function`
	 *
	 * For everything else, `is_monomorphic::value` will be `false` by
	 * default.
	 *
	 * \ingroup typetraits
	 */
	template<typename>
	struct is_monomorphic {
		static constexpr bool value = false;
	};

	template<typename R, typename...Args>
	struct is_monomorphic<std::function<R(Args...)>> {
		static constexpr bool value = true;
	};

	template<typename R, typename...Args>
	struct is_monomorphic<ftl::function<R(Args...)>> {
		static constexpr bool value = true;
	};

	template<typename R, typename...Args>
	struct is_monomorphic<R(*)(Args...)> {
		static constexpr bool value = true;
	};

	template<typename C, typename R, typename...Args>
	struct is_monomorphic<R (C::*)(Args...)> {
		static constexpr bool value = true;
	};

	template<typename F, typename...Args>
	struct is_callable {
	private:

		template<typename G, typename...Qs>
		static auto check(G g, Qs...qs) -> decltype(g(qs...));

		static _dtl::no check(...);

	public:
		static constexpr bool value = 
			!std::is_same<
				_dtl::no,
				decltype(check(std::declval<F>(), std::declval<Args>()...))
			>::value;

		using type =
			decltype(check(std::declval<F>(), std::declval<Args>()...));
	};
}

#endif

